#region Using Statements
using System.Windows.Forms;
using System;
using System.Linq;
using DirectZZT.Shared.Scripting;
using System.Text;
using System.IO;
using System.Drawing;
#endregion

namespace DirectZZT.Forms
{
    /// <summary>
    /// Assists when dealing with scripts.
    /// </summary>
    public partial class ScriptDebuggerWindow : Form
    {
        #region Delegates

        private delegate void InvokeSetText(string text);
        private delegate void SetScriptCodeLineColor(int line);

        #endregion

        #region Static fields

        /// <summary>
        /// Whether or not this window is opened.
        /// </summary>
        internal static bool IsOpen { get; set; }

        #endregion

        #region Fields

        private IScript _selectedScript;
        private ListViewItem _highlighted;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the ScriptDebuggerWindow class.
        /// </summary>
        public ScriptDebuggerWindow()
        {
            InitializeComponent();
        }

        #endregion

        #region Methods

        /// <summary>
        /// Called when the dialog is shown.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnShown(EventArgs e)
        {
            base.OnShown(e);
            IsOpen = true;

            ScriptDebugger.EventOcurred += ScriptDebugger_EventOcurred;
            ScriptDebugger.ScriptRegistered += ScriptDebugger_ScriptRegistered;
            ScriptDebugger.ScriptUnregistered += ScriptDebugger_ScriptUnregistered;
            ScriptDebugger.ScriptsCleared += ScriptDebugger_ScriptsCleared;

            chkIgnoreEvents.Checked = !ScriptDebugger.EnableEvents;
            chkTrackExecution.Checked = ScriptDebugger.TrackExecution;

            // retrieve all scripts
            RetrieveScripts();
        }

        private void RetrieveScripts()
        {
            lsvRegisteredScripts.Items.Clear();
            _selectedScript = null;
            txtOutput.Clear();
            lsvScriptCode.Items.Clear();
            _highlighted = null;

            foreach (IScript script in ScriptDebugger.GetRegisteredScripts())
            {
                ListViewItem lvi = new ListViewItem(script.DebugName);
                lvi.Tag = script;
                lvi.SubItems.Add(GetStatusText(script.State));
                lsvRegisteredScripts.Items.Add(lvi);
            }
        }

        /// <summary>
        /// Called when the dialog is closed.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);


            IsOpen = false;
        }

        /// <summary>
        /// Called when the window closes.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
        {
            base.OnClosing(e);

            ScriptDebugger.EventOcurred -= ScriptDebugger_EventOcurred;
            ScriptDebugger.ScriptRegistered -= ScriptDebugger_ScriptRegistered;
            ScriptDebugger.ScriptUnregistered -= ScriptDebugger_ScriptUnregistered;
            ScriptDebugger.ScriptsCleared -= ScriptDebugger_ScriptsCleared;
            if (chkTrackExecution.Checked)
            {
                ScriptDebugger.ExecutionLineChanged -= ScriptDebugger_ExecutionLineChanged;
            }
        }

        private void ScriptDebugger_EventOcurred(object sender, ScriptDebugger.ScriptDebuggerEventEventArgs e)
        {
            if (e.Script == _selectedScript && !chkIgnoreEvents.Checked)
            {
                StringBuilder sb = new StringBuilder(txtOutput.Text);
                sb.Insert(0, string.Format("[{0}]. Script: {1}, Parameters: {2}{3}", e.EventType, e.Script.DebugName, string.Join(";", e.Parameters), Environment.NewLine));

                if (txtOutput.InvokeRequired)
                {
                    InvokeSetText t = (v) =>
                    {
                        txtOutput.Text = v;
                    };

                    txtOutput.Invoke(t, sb.ToString());
                }
                else
                {
                    txtOutput.Text = sb.ToString();
                }
            }
        }

        private void ScriptDebugger_ScriptRegistered(object sender, ScriptDebugger.ScriptDebuggerEventArgs e)
        {
            RetrieveScripts();
        }

        private void ScriptDebugger_ScriptUnregistered(object sender, ScriptDebugger.ScriptDebuggerEventArgs e)
        {
            //RetrieveScripts();
        }

        private void ScriptDebugger_ScriptsCleared(object sender, EventArgs e)
        {
            RetrieveScripts();
        }

        private void lsvRegisteredScripts_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lsvRegisteredScripts.SelectedItems.Count == 0)
            {
                _selectedScript = null;
                txtOutput.Clear();

                this.btnBreak.Enabled = false;
                this.btnContinue.Enabled = false;
                this.btnStep.Enabled = false;
                this.lsvScriptCode.Items.Clear();
            }
            else
            {
                ListViewItem lvi = lsvRegisteredScripts.SelectedItems[0];
                _selectedScript = lvi.Tag as IScript;

                this.btnBreak.Enabled = !_selectedScript.State.HasFlag(ScriptState.DEBUG_BREAK);
                this.btnContinue.Enabled = !this.btnBreak.Enabled;
                this.btnStep.Enabled = !this.btnBreak.Enabled;

                foreach (var item in _selectedScript.Script)
                {
                    lsvScriptCode.Items.Add(item);
                }
            }
            _highlighted = null;
        }

        private void btnClearOutput_Click(object sender, EventArgs e)
        {
            this.txtOutput.Clear();
        }

        private void lnkRefreshScripts_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            RetrieveScripts();
        }

        private void btnStep_Click(object sender, EventArgs e)
        {
            if (_selectedScript == null)
            {
                return;
            }

            _selectedScript.DebugStep();
            UpdateScriptStatus(_selectedScript, ScriptState.DEBUG_STEP);
        }

        private void btnBreak_Click(object sender, EventArgs e)
        {
            if (_selectedScript == null)
            {
                return;
            }

            _selectedScript.DebugBreak();
            UpdateScriptStatus(_selectedScript, ScriptState.DEBUG_BREAK);

            this.btnBreak.Enabled = false;
            this.btnContinue.Enabled = !this.btnBreak.Enabled;
            this.btnStep.Enabled = !this.btnBreak.Enabled;
        }

        private void btnContinue_Click(object sender, EventArgs e)
        {
            if (_selectedScript == null)
            {
                return;
            }

            _selectedScript.DebugContinue();
            UpdateScriptStatus(_selectedScript, ScriptState.Running);

            this.btnBreak.Enabled = true;
            this.btnContinue.Enabled = !this.btnBreak.Enabled;
            this.btnStep.Enabled = !this.btnBreak.Enabled;
        }

        private void btnRestart_Click(object sender, EventArgs e)
        {
            if (_selectedScript == null)
            {
                return;
            }

            _selectedScript.DebugRestart();

            this.btnBreak.Enabled = true;
            this.btnContinue.Enabled = !this.btnBreak.Enabled;
            this.btnStep.Enabled = !this.btnBreak.Enabled;
        }

        private void tsmSave_Click(object sender, EventArgs e)
        {
            SaveFileDialog dialog = new SaveFileDialog();
            dialog.InitialDirectory = Application.StartupPath;
            dialog.Filter = "All files (*.*)|*.*";
            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                using (FileStream stream = new FileStream(dialog.FileName, FileMode.Create, FileAccess.Write))
                {
                    using (StreamWriter writer = new StreamWriter(stream))
                    {
                        writer.Write(this.txtOutput.Text);
                    }
                }
            }
        }

        private void UpdateScriptStatus(IScript script, ScriptState status)
        {
            lsvRegisteredScripts.Items.Cast<ListViewItem>().FirstOrDefault(t => (IScript)t.Tag == script).SubItems[1].Text = GetStatusText(status);
        }

        private string GetStatusText(ScriptState status)
        {
            if (status.HasFlag(ScriptState.DEBUG_BREAK)
                || status.HasFlag(ScriptState.DEBUG_STEP))
            {
                return "Halted";
            }
            switch (status)
            {
                case ScriptState.Running:
                    return "Running";
                case ScriptState.DEBUG_BREAK:
                case ScriptState.DEBUG_STEP:
                    return "Halted";
                case ScriptState.Faulted:
                    return "Faulted";
                default:
                    return "Finished";
            }
        }

        private void chkTrackExecution_CheckedChanged(object sender, EventArgs e)
        {
            ScriptDebugger.TrackExecution = chkTrackExecution.Checked;
            if (ScriptDebugger.TrackExecution)
            {
                ScriptDebugger.ExecutionLineChanged += ScriptDebugger_ExecutionLineChanged;
            }
            else
            {
                ScriptDebugger.ExecutionLineChanged -= ScriptDebugger_ExecutionLineChanged;
            }
        }

        private void ScriptDebugger_ExecutionLineChanged(object sender, ScriptDebugger.ScriptExecutionLineChangedEventArgs e)
        {
            if (_selectedScript == e.Script)
            {
                try
                {
                    // highlight lines in script code list
                    if (lsvScriptCode.InvokeRequired)
                    {
                        SetScriptCodeLineColor t = (v) =>
                        {
                            SetLineColor(v);
                        };

                        if (this.IsHandleCreated)
                        {
                            lsvScriptCode.Invoke(t, e.Line);
                        }
                    }
                    else
                    {
                        SetLineColor(e.Line);
                    }

                }
                catch (Exception)
                {

                }
            }
        }

        private void SetLineColor(int line)
        {
            if (!chkTrackExecutionShowCoverage.Checked)
            {
                // find previously highlighted element and revert it
                if (_highlighted != null)
                {
                    _highlighted.BackColor = Color.White;
                }
            }

            // sometimes after closing and re-opening the window an ArgumentOutOfRangeException might fall,
            // but that one we can safely prevent
            if (lsvScriptCode.Items.Count > 0)
            {
                ListViewItem lvi = lsvScriptCode.Items[line];
                lvi.BackColor = Color.Red;
                _highlighted = lvi;
            }
        }

        private void cmsScriptCode_Opening(object sender, System.ComponentModel.CancelEventArgs e)
        {
            e.Cancel = lsvScriptCode.SelectedItems.Count == 0;
        }

        private void tsmEditLine_Click(object sender, EventArgs e)
        {
            lsvScriptCode.LabelEdit = true;
            lsvScriptCode.SelectedItems[0].BeginEdit();
        }

        private void lsvScriptCode_AfterLabelEdit(object sender, LabelEditEventArgs e)
        {
            if (!string.IsNullOrWhiteSpace(e.Label))
            {

                _selectedScript.DebugModifyLineContents(e.Item, e.Label);
                // re-apply text
                lsvScriptCode.Items[e.Item].Text = _selectedScript.Script[e.Item];

            }
            else
            {
                e.CancelEdit = true;
            }

            lsvScriptCode.LabelEdit = false;
        }

        #endregion

        #region Static methods

        /// <summary>
        /// Initializes the script debugger.
        /// </summary>
        internal static void Initialize()
        {
        }

        /// <summary>
        /// Shuts down the script debugger.
        /// </summary>
        internal static void Shutdown()
        {
        }

        #endregion
    }
}